{
 "cells": [
  {
   "metadata": {},
   "cell_type": "markdown",
   "source": [
    "## 使用CPU来测试一个简单的CNN模型\n",
    "### 环境配置：检查并设置计算设备（GPU/CPU），确保模型和数据在同一设备上运行。\n",
    "### 数据准备：加载 MNIST 数据集，通过DataLoader实现批量加载，并使用transform进行预处理（格式转换、归一化）。\n",
    "### 模型构建：定义一个包含 2 个卷积层、2 个池化层和 1 个全连接层的 CNN，通过forward方法实现前向传播。\n",
    "## 训练流程：\n",
    "### 每个 epoch 遍历训练集，通过前向传播计算预测、反向传播求梯度、优化器更新参数。\n",
    "### 每 300 个批次打印一次损失，监控训练状态。\n",
    "### 测试流程：每个 epoch 结束后，在测试集上计算准确率，评估模型泛化能力。\n"
   ],
   "id": "70a13765b215476d"
  },
  {
   "cell_type": "code",
   "id": "initial_id",
   "metadata": {
    "collapsed": true,
    "ExecuteTime": {
     "end_time": "2025-10-28T11:03:14.443614Z",
     "start_time": "2025-10-28T11:03:13.173876Z"
    }
   },
   "source": [
    "# 导入PyTorch深度学习框架的核心模块，包含张量操作、神经网络等基础功能\n",
    "import torch\n",
    "# 导入PyTorch的函数式接口模块，通常缩写为F，提供激活函数、池化等操作（函数式API）\n",
    "import torch.nn.functional as F\n",
    "# 从torchvision导入transforms模块，用于图像预处理（如格式转换、归一化等）\n",
    "from torchvision import transforms\n",
    "# 从torchvision导入datasets模块，包含常用的公开数据集（如MNIST、CIFAR等）\n",
    "from torchvision import datasets\n",
    "# 从torch.utils.data导入DataLoader，用于批量加载数据并支持多线程\n",
    "from torch.utils.data import DataLoader\n",
    "# 导入PyTorch的优化器模块，包含各种优化算法（如SGD、Adam等）\n",
    "import torch.optim as optim"
   ],
   "outputs": [],
   "execution_count": 1
  },
  {
   "metadata": {
    "ExecuteTime": {
     "end_time": "2025-10-28T11:03:15.458717Z",
     "start_time": "2025-10-28T11:03:15.456251Z"
    }
   },
   "cell_type": "code",
   "source": [
    "# 检查是否有可用的CUDA设备（GPU），用于加速训练\n",
    "if torch.cuda.is_available():\n",
    "    # 若有GPU，将设备设置为第一个GPU（cuda:0）\n",
    "    device = torch.device(\"cuda:0\")\n",
    "    print(\"CUDA results is:\", device)  # 打印使用的GPU设备\n",
    "else:\n",
    "    # 若无GPU，使用CPU\n",
    "    device = torch.device(\"cpu\")\n",
    "    print(\"CUDA results is:\", device)  # 打印使用的CPU设备"
   ],
   "id": "57a9d0ef73dc1b1d",
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "CUDA results is: cpu\n"
     ]
    }
   ],
   "execution_count": 2
  },
  {
   "metadata": {
    "ExecuteTime": {
     "end_time": "2025-10-28T11:03:21.178156Z",
     "start_time": "2025-10-28T11:03:21.152174Z"
    }
   },
   "cell_type": "code",
   "source": [
    "# 第一步：准备数据（数据加载与预处理）\n",
    "# 定义批量大小（每次训练/测试输入模型的样本数量）\n",
    "# 批量大小影响训练效率和模型收敛，64是常用的适中值\n",
    "batch_size = 64\n",
    "\n",
    "# 定义图像预处理流水线（Compose将多个预处理操作组合成一个流程）\n",
    "transform = transforms.Compose([\n",
    "    transforms.ToTensor(),  # 将PIL图像（0-255的像素值）转换为PyTorch张量（0-1的浮点数）\n",
    "    # 对张量进行归一化：output = (input - mean) / std\n",
    "    # MNIST数据集的均值为0.1307，标准差为0.3081（预先统计好的常用值）\n",
    "    # 归一化可加速模型收敛，使不同特征在同一量级\n",
    "    transforms.Normalize((0.1307, ), (0.3081, ))\n",
    "])\n",
    "\n",
    "# 加载MNIST训练数据集\n",
    "# root：数据集保存路径（../dataset/mnist/表示上级目录的dataset/mnist文件夹）\n",
    "# train=True：加载训练集（包含60000张手写数字图像）\n",
    "# download=True：若本地没有数据集，则自动从网络下载\n",
    "# transform=transform：应用上面定义的预处理流程\n",
    "train_dataset = datasets.MNIST(root='../dataset/mnist/',train=True,download=True,transform=transform)\n",
    "\n",
    "# 加载MNIST测试数据集\n",
    "# train=False：加载测试集（包含10000张手写数字图像，用于评估模型性能）\n",
    "test_dataset = datasets.MNIST(root='../dataset/mnist/',train=False,download=True,transform=transform)\n",
    "\n",
    "# 创建训练数据加载器\n",
    "# 将train_dataset按batch_size分成多个批次，shuffle=True表示每个epoch前打乱数据顺序\n",
    "# 作用：打乱数据可避免模型学习到数据顺序的无关特征，提高泛化能力\n",
    "train_loader = DataLoader(train_dataset, shuffle=True, batch_size=batch_size)\n",
    "\n",
    "# 创建测试数据加载器\n",
    "# 测试时无需打乱数据（shuffle=False），按原顺序加载即可\n",
    "test_loader = DataLoader(test_dataset, shuffle=False, batch_size=batch_size)"
   ],
   "id": "8c7f24d34cec6b9c",
   "outputs": [],
   "execution_count": 4
  },
  {
   "metadata": {
    "ExecuteTime": {
     "end_time": "2025-10-28T11:03:26.097695Z",
     "start_time": "2025-10-28T11:03:26.086457Z"
    }
   },
   "cell_type": "code",
   "source": [
    "# 第二步：构建模型（定义CNN网络结构）\n",
    "# 定义一个基于CNN（卷积神经网络）的模型类，继承自torch.nn.Module（PyTorch中所有模型的基类）\n",
    "class cnnNet(torch.nn.Module):\n",
    "    # 类的初始化方法，用于定义模型的层结构（只在实例化时执行一次）\n",
    "    def __init__(self):\n",
    "        # 调用父类（torch.nn.Module）的初始化方法，确保模型能正确注册参数和子模块\n",
    "        super(cnnNet, self).__init__()\n",
    "\n",
    "        # 定义第一个卷积层（Conv2d用于处理2D图像数据）\n",
    "        # in_channels=1：输入图像的通道数（MNIST是灰度图，通道数为1；彩色图如RGB为3）\n",
    "        # out_channels=10：卷积核的数量（输出特征图的通道数），10个卷积核提取10种基础特征\n",
    "        # kernel_size=5：卷积核的尺寸为5x5（滑动窗口大小）\n",
    "        self.conv1 = torch.nn.Conv2d(in_channels=1, out_channels=10, kernel_size=5)\n",
    "\n",
    "        # 定义第二个卷积层（更深层次的特征提取）\n",
    "        # in_channels=10：输入通道数需与上一层的输出通道数一致（10）\n",
    "        # out_channels=20：输出通道数为20，提取更复杂的组合特征\n",
    "        # kernel_size=5：仍使用5x5卷积核\n",
    "        self.conv2 = torch.nn.Conv2d(in_channels=10, out_channels=20, kernel_size=5)\n",
    "\n",
    "        # 定义最大池化层（MaxPool2d用于降维）\n",
    "        # kernel_size=2：池化窗口大小为2x2，对窗口内的像素取最大值\n",
    "        # 作用：减少特征图尺寸（缩小为原来的1/2），降低计算量，同时增强特征的平移不变性\n",
    "        self.pooling = torch.nn.MaxPool2d(2)\n",
    "\n",
    "        # 定义全连接层（Linear层用于分类决策）\n",
    "        # in_features=320：输入特征的维度（由卷积层输出展平后得到，计算过程见forward方法）\n",
    "        # out_features=10：输出维度为10（对应MNIST的10个数字类别）\n",
    "        self.fc = torch.nn.Linear(320, 10)\n",
    "\n",
    "    # 定义模型的前向传播逻辑（必须实现，PyTorch通过该方法计算输出）\n",
    "    # x：输入的批量图像数据，形状为[batch_size, channels, height, width]\n",
    "    # 例如MNIST输入为[64, 1, 28, 28]（64个样本，1通道，28x28像素）\n",
    "    def forward(self, x):\n",
    "        # 获取输入数据的批量大小（即一次输入的样本数量，如64）\n",
    "        batch_size = x.size(0)\n",
    "\n",
    "        # 第一层：卷积 -> 池化 -> 激活\n",
    "        # 1. self.conv1(x)：对输入x应用第一个卷积层\n",
    "        #    输入形状[64, 1, 28, 28] -> 输出形状[64, 10, 24, 24]\n",
    "        #    计算方式：(28 - 5 + 1) = 24（卷积后尺寸=输入尺寸-卷积核尺寸+1）\n",
    "        # 2. self.pooling(...)：对卷积结果做2x2最大池化\n",
    "        #    输出形状[64, 10, 12, 12]（24/2=12，池化后尺寸=输入尺寸/池化窗口大小）\n",
    "        # 3. F.relu(...)：应用ReLU激活函数，将负数置为0，引入非线性\n",
    "        #    作用：使模型能拟合复杂的非线性关系，输出形状不变\n",
    "        x = F.relu(self.pooling(self.conv1(x)))\n",
    "\n",
    "        # 第二层：卷积 -> 池化 -> 激活\n",
    "        # 1. self.conv2(x)：对上层输出应用第二个卷积层\n",
    "        #    输入形状[64, 10, 12, 12] -> 输出形状[64, 20, 8, 8]\n",
    "        #    计算方式：(12 - 5 + 1) = 8\n",
    "        # 2. self.pooling(...)：2x2池化后，尺寸8/2=4\n",
    "        #    输出形状[64, 20, 4, 4]\n",
    "        # 3. F.relu(...)：ReLU激活，形状不变\n",
    "        x = F.relu(self.pooling(self.conv2(x)))\n",
    "\n",
    "        # 将卷积层输出的多维特征图展平为一维向量（全连接层要求输入为一维）\n",
    "        # x.view(batch_size, -1)：将x重塑为[batch_size, 特征数]\n",
    "        # -1表示自动计算特征数：20（通道）*4（高）*4（宽）=320\n",
    "        # 输出形状[64, 320]\n",
    "        x = x.view(batch_size, -1)\n",
    "\n",
    "        # 全连接层计算：将320维特征映射到10维输出\n",
    "        # 输出形状[64, 10]，每个维度对应一个类别的预测分数（未归一化的概率）\n",
    "        x = self.fc(x)\n",
    "\n",
    "        # 返回最终的预测结果\n",
    "        return x\n",
    "\n",
    "\n",
    "# 实例化定义好的CNN模型\n",
    "model = cnnNet()\n",
    "# 将模型移动到指定设备（GPU或CPU），确保模型计算在目标设备上进行\n",
    "device = torch.device(\"cuda:0\" if torch.cuda.is_available() else \"cpu\")\n",
    "model.to(device)"
   ],
   "id": "8f840585893c8aa4",
   "outputs": [
    {
     "data": {
      "text/plain": [
       "cnnNet(\n",
       "  (conv1): Conv2d(1, 10, kernel_size=(5, 5), stride=(1, 1))\n",
       "  (conv2): Conv2d(10, 20, kernel_size=(5, 5), stride=(1, 1))\n",
       "  (pooling): MaxPool2d(kernel_size=2, stride=2, padding=0, dilation=1, ceil_mode=False)\n",
       "  (fc): Linear(in_features=320, out_features=10, bias=True)\n",
       ")"
      ]
     },
     "execution_count": 5,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "execution_count": 5
  },
  {
   "metadata": {
    "ExecuteTime": {
     "end_time": "2025-10-27T07:55:25.805698Z",
     "start_time": "2025-10-27T07:55:25.802380Z"
    }
   },
   "cell_type": "code",
   "source": [
    "# 第三步：构建损失函数和优化器\n",
    "# 定义损失函数：交叉熵损失（CrossEntropyLoss）\n",
    "# 适用于多分类任务，内部已包含SoftMax激活和负对数似然损失，直接输入未归一化的分数即可\n",
    "criterion = torch.nn.CrossEntropyLoss()\n",
    "\n",
    "# 定义优化器：随机梯度下降（SGD）\n",
    "# model.parameters()：需要优化的模型参数（权重和偏置）\n",
    "# lr=0.01：学习率（控制参数更新的步长，过大可能不收敛，过小收敛慢）\n",
    "# momentum=0.5：动量参数（模拟物理中的动量，加速收敛并减少震荡）\n",
    "optimizer = optim.SGD(model.parameters(), lr=0.01, momentum=0.5)"
   ],
   "id": "e36a579316e1f0e",
   "outputs": [],
   "execution_count": 21
  },
  {
   "metadata": {
    "ExecuteTime": {
     "end_time": "2025-10-27T07:55:27.291694Z",
     "start_time": "2025-10-27T07:55:27.286851Z"
    }
   },
   "cell_type": "code",
   "source": [
    "# 定义训练函数（每个epoch的训练过程）\n",
    "def train(epoch):\n",
    "    # 初始化累计损失（用于打印训练进度）\n",
    "    running_loss = 0.0\n",
    "    # 遍历训练数据加载器中的每个批次\n",
    "    # batch_idx：批次索引，data：每个批次的数据（包含输入和标签）\n",
    "    for batch_idx, data in enumerate(train_loader, 0):\n",
    "        # 从data中分离输入图像（inputs）和对应的标签（target）\n",
    "        inputs, target = data\n",
    "        # 将输入和标签移动到目标设备（与模型同设备）\n",
    "        inputs, target = inputs.to(device), target.to(device)\n",
    "\n",
    "        # 清空优化器的梯度（PyTorch会累积梯度，每次迭代前需清零）\n",
    "        optimizer.zero_grad()\n",
    "\n",
    "        # 前向传播：将输入传入模型，得到预测输出\n",
    "        outputs = model(inputs)\n",
    "        # 计算损失：预测输出与真实标签的差异\n",
    "        loss = criterion(outputs, target)\n",
    "        # 反向传播：计算损失对各参数的梯度（自动求导）\n",
    "        loss.backward()\n",
    "        # 优化器更新参数：根据梯度调整模型权重\n",
    "        optimizer.step()\n",
    "\n",
    "        # 累加当前批次的损失值（.item()将张量转换为Python数值）\n",
    "        running_loss += loss.item()\n",
    "        # 每300个批次打印一次平均损失（监控训练进度）\n",
    "        if batch_idx % 300 == 299:\n",
    "            # 打印信息：当前 epoch+1（从1开始）、批次索引+1、平均损失（除以2000是为了缩放数值）\n",
    "            print('[%d, %5d] loss: %.3f' % (epoch + 1, batch_idx + 1, running_loss / 2000))\n",
    "            # 重置累计损失，为下一轮累计做准备\n",
    "            running_loss = 0.0"
   ],
   "id": "982b187c640afa9d",
   "outputs": [],
   "execution_count": 22
  },
  {
   "metadata": {
    "ExecuteTime": {
     "end_time": "2025-10-27T07:55:30.188657Z",
     "start_time": "2025-10-27T07:55:30.183323Z"
    }
   },
   "cell_type": "code",
   "source": [
    "# 定义测试函数（评估模型在测试集上的性能）\n",
    "def test():\n",
    "    # 初始化正确预测的样本数和总样本数\n",
    "    correct = 0\n",
    "    total = 0\n",
    "    # with torch.no_grad()：禁用梯度计算（测试时不需要求导，节省内存和计算资源）\n",
    "    with torch.no_grad():\n",
    "        # 遍历测试数据加载器中的每个批次\n",
    "        for data in test_loader:\n",
    "            # 分离输入图像和标签，并移动到目标设备\n",
    "            inputs, target = data\n",
    "            inputs, target = inputs.to(device), target.to(device)\n",
    "            # 前向传播：得到预测输出\n",
    "            outputs = model(inputs)\n",
    "            # 从输出中获取预测结果：torch.max返回最大值和对应的索引\n",
    "            # dim=1：沿第1维（类别维度）取最大值，predicted为预测的类别索引\n",
    "            _, predicted = torch.max(outputs.data, dim=1)\n",
    "            # 累加总样本数（当前批次的样本数）\n",
    "            total += target.size(0)\n",
    "            # 累加正确预测的样本数（预测类别与真实标签一致的数量）\n",
    "            correct += (predicted == target).sum().item()\n",
    "    accuracy = 100 * correct / total  # 计算准确率（百分比）\n",
    "    # 打印测试集准确率：（正确数/总数）*100%，并显示具体的正确数和总数\n",
    "    print('Accuracy on test set: %d %% [%d/%d]' % (100 * correct / total, correct, total))\n",
    "    return accuracy  # 返回准确率，用于绘图"
   ],
   "id": "4ea79f3c8a870ae0",
   "outputs": [],
   "execution_count": 23
  },
  {
   "metadata": {},
   "cell_type": "code",
   "source": [
    "# 主程序入口：当脚本直接运行时执行以下代码\n",
    "if __name__ == '__main__':\n",
    "    # 训练10个epoch（完整遍历训练集10次）\n",
    "    for epoch in range(10):\n",
    "        # 执行训练函数，训练一个epoch\n",
    "        train(epoch)\n",
    "        # 每个epoch训练结束后，在测试集上评估一次模型性能\n",
    "        test()"
   ],
   "id": "1490bedeb7c96eff",
   "outputs": [],
   "execution_count": null
  }
 ],
 "metadata": {
  "kernelspec": {
   "display_name": "Python 3",
   "language": "python",
   "name": "python3"
  },
  "language_info": {
   "codemirror_mode": {
    "name": "ipython",
    "version": 2
   },
   "file_extension": ".py",
   "mimetype": "text/x-python",
   "name": "python",
   "nbconvert_exporter": "python",
   "pygments_lexer": "ipython2",
   "version": "2.7.6"
  }
 },
 "nbformat": 4,
 "nbformat_minor": 5
}
